//* ////////////////////////////////////////////////////////////////////////////// */
//*
//
//              INTEL CORPORATION PROPRIETARY INFORMATION
//  This software is supplied under the terms of a license  agreement or
//  nondisclosure agreement with Intel Corporation and may not be copied
//  or disclosed except in  accordance  with the terms of that agreement.
//        Copyright (c) 2010 - 2013 Intel Corporation. All Rights Reserved.
//
//
//*/
#include <windows.h>

#include "sample_multi_transcode.h"

using namespace std;
using namespace TranscodingSample;

Launcher::Launcher():m_StartTime(0),
                     m_bUseD3D9(false),
                     m_bUseD3D11(false)
{
    m_MinRequiredAPIVersion.Major = 1;
    m_MinRequiredAPIVersion.Minor = 0;
} // Launcher::Launcher()

Launcher::~Launcher()
{
    Close();
} // Launcher::~Launcher()

mfxStatus Launcher::Init(int argc, msdk_char *argv[])
{
    mfxStatus sts;
    mfxU32 i = 0;
    SafetySurfaceBuffer* pBuffer = NULL;
    mfxU32 BufCounter = 0;
    HANDLE hdl = NULL;
    sInputParams    InputParams;

    //parent transcode pipeline
    CTranscodingPipeline *pParentPipeline = NULL;
    // source transcode pipeline use instead parent in heterogeneous pipeline
    CTranscodingPipeline *pSinkPipeline = NULL;

    // parse input par file
    sts = m_parser.ParseCmdLine(argc, argv);
    MSDK_CHECK_PARSE_RESULT(sts, MFX_ERR_NONE, sts); 

    // get parameters for each session from parser
    while(m_parser.GetNextSessionParams(InputParams))
    {
        m_InputParamsArray.push_back(InputParams);
        InputParams.Reset();
    }

    // check correctness of input parameters
    sts = VerifyCrossSessionsOptions();
    MSDK_CHECK_RESULT(sts, MFX_ERR_NONE, sts); 
    
    if (m_bUseD3D9)
    {         
        m_pAllocParam.reset(new D3DAllocatorParams);                     
        m_hwdev.reset(new CD3D9Device()); 
        sts = m_hwdev->Init(NULL, 1, GetMSDKAdapterNumber() );
        MSDK_CHECK_RESULT(sts, MFX_ERR_NONE, sts);
        sts = m_hwdev->GetHandle(MFX_HANDLE_D3D9_DEVICE_MANAGER, &hdl);
        MSDK_CHECK_RESULT(sts, MFX_ERR_NONE, sts);
        // set Device Manager to external dx9 allocator
        D3DAllocatorParams *pD3DParams = dynamic_cast<D3DAllocatorParams*>(m_pAllocParam.get());
        pD3DParams->pManager =(IDirect3DDeviceManager9*)hdl;
    }
    else if (m_bUseD3D11)
    {           
#if MFX_D3D11_SUPPORT
        m_pAllocParam.reset(new D3D11AllocatorParams);  
        m_hwdev.reset(new CD3D11Device()); 
        sts = m_hwdev->Init(NULL, 1, GetMSDKAdapterNumber() );
        MSDK_CHECK_RESULT(sts, MFX_ERR_NONE, sts);
        sts = m_hwdev->GetHandle(MFX_HANDLE_D3D11_DEVICE, &hdl);
        MSDK_CHECK_RESULT(sts, MFX_ERR_NONE, sts);
        // set Device to external dx11 allocator
        D3D11AllocatorParams *pD3D11Params = dynamic_cast<D3D11AllocatorParams*>(m_pAllocParam.get());
        pD3D11Params->pDevice =(ID3D11Device*)hdl;
#endif
    }
    else
    {
        m_pAllocParam.reset(new mfxAllocatorParams);
    }

    // each pair of source and sink has own safety buffer
    sts = CreateSafetyBuffers();
    MSDK_CHECK_RESULT(sts, MFX_ERR_NONE, sts);

    // create sessions, allocators 
    for (i = 0; i < m_InputParamsArray.size(); i++)
    {
        GeneralAllocator* pAllocator = new GeneralAllocator;
        sts = pAllocator->Init(m_pAllocParam.get());
        MSDK_CHECK_RESULT(sts, MFX_ERR_NONE, sts); 
        m_pAllocArray.push_back(pAllocator);

        std::auto_ptr<ThreadTranscodeContext> pThreadPipeline(new ThreadTranscodeContext);
        // extend BS processing init
        m_pExtBSProcArray.push_back(new FileBitstreamProcessor);
        pThreadPipeline->pPipeline.reset(new CTranscodingPipeline);        
     
        pThreadPipeline->pBSProcessor = m_pExtBSProcArray.back();
        if (Sink == m_InputParamsArray[i].eMode)
        {
            pBuffer = m_pBufferArray[m_pBufferArray.size()-1];
            pSinkPipeline = pThreadPipeline->pPipeline.get();
            sts = m_pExtBSProcArray.back()->Init(m_InputParamsArray[i].strSrcFile, NULL);
            MSDK_CHECK_RESULT(sts, MFX_ERR_NONE, sts); 
        }
        else if (Source == m_InputParamsArray[i].eMode)
        {
            pBuffer = m_pBufferArray[BufCounter];
            BufCounter++;
            sts = m_pExtBSProcArray.back()->Init(NULL, m_InputParamsArray[i].strDstFile);
            MSDK_CHECK_RESULT(sts, MFX_ERR_NONE, sts);
        }
        else 
        {
            sts = m_pExtBSProcArray.back()->Init(&m_InputParamsArray[i]);
            MSDK_CHECK_RESULT(sts, MFX_ERR_NONE, sts);
            pBuffer = NULL;
        }

        // if session has VPP plus ENCODE only (-i::source option)
        // use decode source session as input
        sts = MFX_ERR_MORE_DATA;
        mfxStatus bs_sts;
        if (Source == m_InputParamsArray[i].eMode)
        {

            while(MFX_ERR_MORE_DATA == sts)
            {
                sts = pThreadPipeline->pPipeline->Init(&m_InputParamsArray[i], 
                                                       m_pAllocArray[i], 
                                                       hdl, 
                                                       pSinkPipeline,
                                                       pBuffer,
                                                       m_pExtBSProcArray.back(),
                                                       m_MinRequiredAPIVersion);
                if (MFX_ERR_NONE <= sts)
                    break;
                else if (MFX_ERR_MORE_DATA != sts) 
                    return sts; // error
                bs_sts = m_pExtBSProcArray.back()->PrepareBitstream();
                MSDK_CHECK_RESULT(bs_sts, MFX_ERR_NONE, bs_sts);

            }
        }
        else
        {
            while(MFX_ERR_MORE_DATA == sts)
            {             
                sts =  pThreadPipeline->pPipeline->Init(&m_InputParamsArray[i], 
                                                        m_pAllocArray[i], 
                                                        hdl, 
                                                        pParentPipeline,
                                                        pBuffer,
                                                        m_pExtBSProcArray.back(),
                                                        m_MinRequiredAPIVersion);
                if (MFX_ERR_NONE <= sts)
                    break;
                else if (MFX_ERR_MORE_DATA != sts) 
                    return sts; // error
                bs_sts = m_pExtBSProcArray.back()->PrepareBitstream();
                MSDK_CHECK_RESULT(bs_sts, MFX_ERR_NONE, bs_sts);

            }


        }

        MSDK_CHECK_RESULT(sts, MFX_ERR_NONE, sts);         

        if (!pParentPipeline && m_InputParamsArray[i].bIsJoin)
            pParentPipeline = pThreadPipeline->pPipeline.get();

        // set the session's start status (like it is waiting)
        pThreadPipeline->startStatus = MFX_WRN_DEVICE_BUSY;
        // set other session's parameters
        pThreadPipeline->implType = m_InputParamsArray[i].libType;
        m_pSessionArray.push_back(pThreadPipeline.release());

        mfxVersion ver = {0};
        sts = m_pSessionArray[i]->pPipeline->QueryMFXVersion(&ver);
        MSDK_CHECK_RESULT(sts, MFX_ERR_NONE, sts);

        PrintInfo(i, &m_InputParamsArray[i], &ver);            
    }

    for (i = 0; i < m_InputParamsArray.size(); i++)
    {
        sts = m_pSessionArray[i]->pPipeline->CompleteInit();
        MSDK_CHECK_RESULT(sts, MFX_ERR_NONE, sts);

        if (m_pSessionArray[i]->pPipeline->GetJoiningFlag())
            msdk_printf(MSDK_STRING("Session %d was joined with other sessions\n"), i);
        else
            msdk_printf(MSDK_STRING("Session %d was NOT joined with other sessions\n"), i);
    }

    msdk_printf(MSDK_STRING("\n"));

    return sts;

} // mfxStatus Launcher::Init()

void Launcher::Run()
{
    mfxU32 totalSessions;

    msdk_printf(MSDK_STRING("Transcoding started\n"));

    // mark start time
    m_StartTime = GetTick();

    // get parallel sessions parameters
    totalSessions = (mfxU32) m_pSessionArray.size();

    mfxU32 i;
    mfxStatus sts;
    
    MSDKThread * pthread = NULL;

    for (i = 0; i < totalSessions; i++)
    {
        pthread = new MSDKThread(sts, ThranscodeRoutine, (void *)m_pSessionArray[i]);

        m_HDLArray.push_back(pthread);
    }   
    
    for (i = 0; i < m_pSessionArray.size(); i++)
    {
        m_HDLArray[i]->Wait();        
    }

    msdk_printf(MSDK_STRING("\nTranscoding finished\n"));

} // mfxStatus Launcher::Init()

mfxStatus Launcher::ProcessResult()
{
    FILE* pPerfFile = m_parser.GetPerformanceFile();
    msdk_printf(MSDK_STRING("\nCommon transcoding time is  %.2f sec \n"), GetTime(m_StartTime));    

    m_parser.PrintParFileName();

    if (pPerfFile)
    {
        msdk_fprintf(pPerfFile, MSDK_STRING("Common transcoding time is  %.2f sec \n"), GetTime(m_StartTime));
    }

    // get result
    bool SuccessTranscode = true;
    mfxU32 i;
    for (i = 0; i < m_pSessionArray.size(); i++)
    {
        mfxStatus sts = m_pSessionArray[i]->transcodingSts;
        if (MFX_ERR_NONE != sts)
        {
            SuccessTranscode = false;
            msdk_printf(MSDK_STRING("MFX session %d transcoding FAILED:\nProcessing time: %.2f sec \nNumber of processed frames: %d\n"), 
                i, 
                m_pSessionArray[i]->working_time, 
                m_pSessionArray[i]->numTransFrames);
            if (pPerfFile)
            {
                msdk_fprintf(pPerfFile, MSDK_STRING("MFX session %d transcoding FAILED:\nProcessing time: %.2f sec \nNumber of processed frames: %d\n"), 
                    i, 
                    m_pSessionArray[i]->working_time, 
                    m_pSessionArray[i]->numTransFrames);
            }
            
        }
        else
        {
            msdk_printf(MSDK_STRING("MFX session %d transcoding PASSED:\nProcessing time: %.2f sec \nNumber of processed frames: %d\n"), 
                i, 
                m_pSessionArray[i]->working_time, 
                m_pSessionArray[i]->numTransFrames);
            if (pPerfFile)
            {
                msdk_fprintf(pPerfFile, MSDK_STRING("MFX session %d transcoding PASSED:\nProcessing time: %.2f sec \nNumber of processed frames: %d\n"), 
                    i, 
                    m_pSessionArray[i]->working_time, 
                    m_pSessionArray[i]->numTransFrames);
            }
        }

        if (pPerfFile)
        {
            if (Native == m_InputParamsArray[i].eMode || Sink == m_InputParamsArray[i].eMode)
            {
                msdk_fprintf(pPerfFile, MSDK_STRING("Input stream: %s\n"), m_InputParamsArray[i].strSrcFile);
            }
            else
                msdk_fprintf(pPerfFile, MSDK_STRING("Input stream: from parent session\n"));
            msdk_fprintf(pPerfFile, MSDK_STRING("\n"));
        }


    }

    if (SuccessTranscode)
    {
        msdk_printf(MSDK_STRING("\nThe test PASSED\n"));
        if (pPerfFile)
        {
            msdk_fprintf(pPerfFile, MSDK_STRING("\nThe test PASSED\n"));
        }
        return MFX_ERR_NONE;
    }
    else
    {
        msdk_printf(MSDK_STRING("\nThe test FAILED\n"));
        if (pPerfFile)
        {
            msdk_fprintf(pPerfFile, MSDK_STRING("\nThe test FAILED\n"));
        }
        return MFX_ERR_UNKNOWN;
    }
} // mfxStatus Launcher::ProcessResult()

// to compare mfxVersions
bool operator <(const mfxVersion &left, const mfxVersion &right)
{
    if (left.Major < right.Major)
        return true;
    else if (left.Major == right.Major)
        return left.Minor < right.Minor;
    return false;
}

mfxStatus Launcher::DetermineMinRequiredAPIVersion(sInputParams *pParams)
{
    MSDK_CHECK_POINTER(pParams, MFX_ERR_NULL_PTR);    
    
    APIChangeFeatures features = {};
    features.MVCDecode = features.MVCEncode = pParams->bIsMVC;
    features.JpegDecode = (MFX_CODEC_JPEG == pParams->DecodeId);
    features.JpegEncode = (MFX_CODEC_JPEG == pParams->EncodeId);
    mfxVersion version = getMinimalRequiredVersion(features);

    // min required version is max version of all transcoding sessions
    if (m_MinRequiredAPIVersion < version)
        m_MinRequiredAPIVersion = version;

    return MFX_ERR_NONE;
}

mfxStatus Launcher::VerifyCrossSessionsOptions()
{
    bool IsSinkPresence = false;
    bool IsSourcePresence = false;
    bool IsHeterSessionJoin = false;
    bool IsFirstInTopology = true;

    for (mfxU32 i = 0; i < m_InputParamsArray.size(); i++)
    {
        if (Source == m_InputParamsArray[i].eMode)
        {
            // topology definition
            if (!IsSinkPresence)
            {
                PrintHelp(NULL, MSDK_STRING("Error in par file. Decode source session must be declared BEFORE encode sinks \n"));
                return MFX_ERR_UNSUPPORTED;
            }
            IsSourcePresence = true;

            if (IsFirstInTopology)
            {
                if (m_InputParamsArray[i].bIsJoin)
                    IsHeterSessionJoin = true;
                else
                    IsHeterSessionJoin = false;
            }
            else
            {
                if (m_InputParamsArray[i].bIsJoin && !IsHeterSessionJoin)
                {
                    PrintHelp(NULL, MSDK_STRING("Error in par file. All heterogeneous sessions must be joined \n"));
                    return MFX_ERR_UNSUPPORTED;
                }
                if (!m_InputParamsArray[i].bIsJoin && IsHeterSessionJoin)
                {
                    PrintHelp(NULL, MSDK_STRING("Error in par file. All heterogeneous sessions must be NOT joined \n"));
                    return MFX_ERR_UNSUPPORTED;
                }
            }
            
            if (IsFirstInTopology)
                IsFirstInTopology = false;

        }
        else if (Sink == m_InputParamsArray[i].eMode)
        {
            if (IsSinkPresence)
            {
                PrintHelp(NULL, MSDK_STRING("Error in par file. Only one source can be used"));
                return MFX_ERR_UNSUPPORTED;
            }
            IsSinkPresence = true;
            
            if (IsFirstInTopology)
            {
                if (m_InputParamsArray[i].bIsJoin)
                    IsHeterSessionJoin = true;
                else
                    IsHeterSessionJoin = false;
            }
            else
            {
                if (m_InputParamsArray[i].bIsJoin && !IsHeterSessionJoin)
                {
                    PrintHelp(NULL, MSDK_STRING("Error in par file. All heterogeneous sessions must be joined \n"));
                    return MFX_ERR_UNSUPPORTED;
                }
                if (!m_InputParamsArray[i].bIsJoin && IsHeterSessionJoin)
                {
                    PrintHelp(NULL, MSDK_STRING("Error in par file. All heterogeneous sessions must be NOT joined \n"));
                    return MFX_ERR_UNSUPPORTED;
                }
            }

            if (IsFirstInTopology)
                IsFirstInTopology = false;
        }
        if (MFX_IMPL_SOFTWARE != m_InputParamsArray[i].libType)
            m_InputParamsArray[i].libType & MFX_IMPL_VIA_D3D11 ? m_bUseD3D11 = true :
                                                                 m_bUseD3D9 = true;

        mfxStatus sts = DetermineMinRequiredAPIVersion(&m_InputParamsArray[i]);
        MSDK_CHECK_RESULT(sts, MFX_ERR_NONE, sts);        
    }

    if (IsSinkPresence && !IsSourcePresence)
    {
        PrintHelp(NULL, MSDK_STRING("Error: Sink must be defined"));
        return MFX_ERR_UNSUPPORTED;
    }
    return MFX_ERR_NONE;

} // mfxStatus Launcher::VerifyCrossSessionsOptions()

mfxStatus Launcher::CreateSafetyBuffers()
{
    SafetySurfaceBuffer* pBuffer     = NULL;
    SafetySurfaceBuffer* pPrevBuffer = NULL;
    
    for (mfxU32 i = 0; i < m_InputParamsArray.size(); i++)
    {
        if (Source == m_InputParamsArray[i].eMode)
        {
            pBuffer = new SafetySurfaceBuffer(pPrevBuffer);
            pPrevBuffer = pBuffer;
            m_pBufferArray.push_back(pBuffer);
        }
    }
    return MFX_ERR_NONE;

} // mfxStatus Launcher::CreateSafetyBuffers

void Launcher::Close()
{
    while(m_pSessionArray.size())
    {
        delete m_pSessionArray[m_pSessionArray.size()-1];
        m_pSessionArray[m_pSessionArray.size() - 1] = NULL;
        delete m_pAllocArray[m_pSessionArray.size()-1];
        m_pAllocArray[m_pSessionArray.size() - 1] = NULL;
        m_pAllocArray.pop_back();
        m_pSessionArray.pop_back();
    }
    while(m_pBufferArray.size())
    {
        delete m_pBufferArray[m_pBufferArray.size()-1];
        m_pBufferArray[m_pBufferArray.size() - 1] = NULL;
        m_pBufferArray.pop_back();
    }

    while(m_pExtBSProcArray.size())
    {
        delete m_pExtBSProcArray[m_pExtBSProcArray.size() - 1];
        m_pExtBSProcArray[m_pExtBSProcArray.size() - 1] = NULL;
        m_pExtBSProcArray.pop_back();
    }

    while (m_HDLArray.size())
    {
        delete m_HDLArray[m_HDLArray.size()-1];
        m_HDLArray.pop_back();
    }
} // void Launcher::Close()

int _tmain(int argc, TCHAR *argv[])
{  
    mfxStatus sts;
    Launcher transcode;
    sts = transcode.Init(argc, argv);
    MSDK_CHECK_PARSE_RESULT(sts, MFX_ERR_NONE, 1);
    
    transcode.Run();

    sts = transcode.ProcessResult();
    MSDK_CHECK_RESULT(sts, MFX_ERR_NONE, 1);

    return 0;
} 

